package assignment3.part1;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import org.jgroups.JChannel;
import org.jgroups.Message;

public class LaggyChat {
    JChannel channel;
    String channel_address;
    String user_name=System.getProperty("user.name", "n/a");
    ArrayList<Integer> ownMessages = new ArrayList<Integer>();

    // Local vector clock.
    HashMap<String, Integer> VectorClock = new HashMap<String, Integer>();
    int ownCounter = 0;

    // Messages that are yet to be displayed
    LinkedList<MessageBody> waitingMessages = new LinkedList<MessageBody>();
    
    private enum VectorCompare{
    	QUEUE, READY
    }
    
    // Method for comparing two vector timestamps
    VectorCompare CompareTimestamps(HashMap<String, Integer> a, HashMap<String, Integer> b, String bSender) {
    	// TODO: implement this    	
    	if(a.get(bSender) + 1 != b.get(bSender)){
    		return VectorCompare.QUEUE;
    	}
    	
    	int dif = 1;
    	
    	if(!channel_address.equals(bSender)){
    		for(String key : a.keySet()){
    			if(!key.equals(bSender)){
    				if(b.containsKey(key)){
    					if(key.equals(channel_address) && a.get(channel_address) < b.get(channel_address)){
    						dif++;
    					}else{
	    					if(a.get(key) < b.get(key)){
	    						dif++;
	    					}
    					}
    				}
    			}
    		}
    	}
    	
    	return (dif == 1 ? VectorCompare.READY : VectorCompare.QUEUE);
    }    
    
    // method for updating the local clock based on some vector timestamp
    public void UpdateClock(HashMap<String, Integer> t) {
    	// TODO: implement this
    	for(String key : t.keySet()){
    		if(VectorClock.containsKey(key)){
    			VectorClock.put(key, Math.max(VectorClock.get(key), t.get(key)));
    		}else{
    			VectorClock.put(key, t.get(key));
    		}
    	}
    }
    
    // Method for testing if a message is ready to be printed.
    // Think about when a message can be printed for the order of messages to make sense:
    // what is required of the vectortimestamp on the message and the local clock?
    public boolean MessageReady(MessageBody m) {    	
    	// TODO: implement this
    	return (CompareTimestamps(VectorClock, m.VectorTimestamp, m.Sender) == VectorCompare.READY);
    }
    
    
    public void newMessage(MessageBody m) {
    	// TODO: Add the sender to the local clock in case it was not known before.
    	if(!VectorClock.containsKey(m.Sender)){
    		VectorClock.put(m.Sender, 0);
    	}
    	
    	// TODO: Check if the message can be printed, if not: add it to the waiting list
    	if(MessageReady(m)){
    		PrintMessage(m);
    	}else{
    		waitingMessages.addLast(m);
    	}
    	
    	// TODO: Can any other waiting messages be printed?
    	if(waitingMessages.size() > 0){
    		ArrayList<MessageBody> sent = new ArrayList<MessageBody>();

    		for(MessageBody mWait : waitingMessages){
    			if(MessageReady(mWait)){
    				sent.add(mWait);
    				PrintMessage(mWait);
    			}
    		}
    		
    		for(MessageBody mRemove : sent){
    			waitingMessages.remove(mRemove);
    		}
    	}
    }
    
    private void PrintMessage(MessageBody m){
    	UpdateClock(m.VectorTimestamp);
    	System.out.println(m.Sender + ": " + m.Message);
    }

    private void start() throws Exception {
        channel=new JChannel();
        channel.setReceiver(new LaggyReceiver(this));
        channel.connect("ChatClusterBaywatch");
        channel.getState(null, 10000);
        channel_address = channel.getAddressAsString();
        VectorClock.put(channel_address, 0); // Initializing own logical time as 0.
        eventLoop();
        channel.close();
    }

    private void eventLoop() {
        BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
        while(true) {
            try {            	
                System.out.print("> "); System.out.flush();
                String line=in.readLine().toLowerCase();
                if(line.startsWith("quit") || line.startsWith("exit")) {
                    break;
                }
                line="[" + user_name + "] " + line;
                MessageBody mb = new MessageBody();
                mb.Message = line;
                mb.Sender = channel_address;
                
                // TODO: Add the vector timestamp to the message and increase the local clock. 
                ownCounter++;
                mb.VectorTimestamp = new HashMap<String, Integer>();
                mb.VectorTimestamp.putAll(VectorClock);
                mb.VectorTimestamp.put(mb.Sender, ownCounter);
                
                Message msg=new Message(null, null, mb);
                channel.send(msg);
            }
            catch(Exception e) {
    			System.out.println("Exception:" + e.getMessage());
    			e.printStackTrace();	
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new LaggyChat().start();
    }
}